package plenprojects.security;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;

import javax.imageio.ImageIO;

import org.springframework.security.crypto.codec.Base64;

import plenprojects.dao.ChatServiceDao;
import plenprojects.exceptions.ImageManagerException;
import plenprojects.exceptions.RegistrationException;
import plenprojects.model.ServerMessage;
import plenprojects.model.UserDetails;
import plenprojects.utils.ImageManager;

public class UserRegistrationValidator {
	
	private final ChatServiceDao dao;
	private final ImageManager manager;
	private final PasswordEncoder encoder;
	
	private int minUsernameLength = 5;
	private int maxUsernameLength = 20;
	
	private int minPasswordLength = 5;
	private int maxPasswordLength = 20;
	
	private int maxDescriptionLength = 100;
	
	private int maxFileSize = 100_000;
	
	private String usernameRegex = "^[A-Za-z0-9]*$";
	private String passwordRegex = "^[A-Za-z0-9]*$";

	public UserRegistrationValidator(ChatServiceDao dao, ImageManager manager, PasswordEncoder encoder) {
		
		this.dao = dao;
		this.manager = manager;
		this.encoder = encoder;
	}

	public UserDetails register(ServerMessage request) throws RegistrationException {
		
		String username = (String)request.getParameter("username");
		String password = (String)request.getParameter("password");
		String description = (String)request.getParameter("description");
		String str = (String)request.getParameter("image");
		
		boolean hasImage = false;
		byte[] imageBytes = null;
		
		if(str != null) {
			
			byte[] bytes = str.getBytes();
			
			imageBytes = Base64.decode(bytes);
			
			hasImage = true;
		}
		
		try {
			
			this.validateUsername(username);
			this.validatePassword(password);
			this.validateDescription(description);
			this.validateImage(imageBytes);
			
		}
		catch(RegistrationException e) {
			
			throw e;
		}
		
		UserDetails ud = new UserDetails();
		
		ud.setUsername(username);
		ud.setHash(encoder.encode(password));
		ud.setDescription(description);
		ud.setHasImage(hasImage);
		
		try {
			
			manager.saveImage(imageBytes, username);
		}
		catch(ImageManagerException e) {
			
			ud.setHasImage(false);
		}
		
		return ud;
	}
	
	private void validateUsername(String username) throws RegistrationException {
		
		if(username == null) {
			
			throw new RegistrationException("Username not set.");
		}
		
		int length = username.length();
		
		if(length < minUsernameLength || length > maxUsernameLength) {
			
			throw new RegistrationException(String.format("Username has to be %d-%d characters long.", minUsernameLength, maxUsernameLength));
		}
		
		if(!username.matches(this.usernameRegex)) {
			
			throw new RegistrationException("Invalid username.");
		}
		
		if(dao.exists(username)) {
			
			throw new RegistrationException(String.format("Username %s is already in use.", username));
		}
	}
	
	private void validatePassword(String password) throws RegistrationException {
		
		if(password == null) {
			
			throw new RegistrationException("Password not set.");
		}
		
		int length = password.length();
		
		if(length < minPasswordLength || length > maxPasswordLength) {
			
			throw new RegistrationException(String.format("Password has to be %d-%d characters long.", minPasswordLength, maxPasswordLength));
		}
		
		if(!password.matches(this.passwordRegex)) {
			
			throw new RegistrationException("Invalid password.");
		}
		
	}
	
	private void validateDescription(String description) throws RegistrationException {
		
		if(description == null) {
			
			return;
		}
		
		if(!(description.length() <= this.maxDescriptionLength)) {
			
			throw new RegistrationException(String.format("Description must have less that %s characters.", this.maxDescriptionLength));
		}
	}
	
	private void validateImage(byte[] bytes) throws RegistrationException {
		
		if(bytes == null) {
			
			return;
		}
		
		if(bytes.length > this.maxFileSize) {
			
			throw new RegistrationException(String.format("Image file must have less than %s bytes length.", this.maxFileSize));
		}
		
		ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
		BufferedImage img = null;
		try {
			
			img = ImageIO.read(stream);
			
		}
		catch(IOException e) {
			
			throw new RegistrationException("Unidentified problem with the image.");
			
		}
		finally {
			
			try {
				
				stream.close();
			}
			catch(IOException e) {
				
			}
		}
		
		if(img == null) {
			
			throw new RegistrationException("The file is not an image.");
		}
	}
	
	public void setMinUsernameLength(int minUsernameLength) {
		
		this.minUsernameLength = minUsernameLength;
	}

	public void setMaxUsernameLength(int maxUsernameLength) {
		
		this.maxUsernameLength = maxUsernameLength;
	}

	public void setMinPasswordLength(int minPasswordLength) {
		
		this.minPasswordLength = minPasswordLength;
	}

	public void setMaxPasswordLength(int maxPasswordLength) {
		
		this.maxPasswordLength = maxPasswordLength;
	}

	public void setMaxDescriptionLength(int maxDescriptionLength) {
		
		this.maxDescriptionLength = maxDescriptionLength;
	}

	public void setMaxFileSize(int maxFileSize) {
		
		this.maxFileSize = maxFileSize;
	}

	public void setUsernameRegex(String usernameRegex) {
		
		this.usernameRegex = usernameRegex;
	}

	public void setPasswordRegex(String passwordRegex) {
		
		this.passwordRegex = passwordRegex;
	}
	
}
